#!/usr/bin/env python
#  Build : 17
#  Written by: Florent Carlier <florent.carlier@gmail.com>
#
# - Version --------------------------------------------------------------
#
#  $Id: pyPokeys56U.py,v 1.0 2012/08/01 00:01:00 fcarlier Exp $
#  
# - License --------------------------------------------------------------
#

import usb
import sys
import time
from version import version

class pokeys56U_USBDevice(object):
    """ Class for talking to PoKeys56U Products USB Device 
        The device encapsulates three interfaces, (index 0) being standard USB HID keyboard, 
                                                  (index 1) being PoKeys communication interface
                                                  (index 2) standard USB HID Joystick.
        Configuration is set or read using the second interface. 
        If more than one PoKeys device is connected to the same host, 
        differentiation at this level is impossible,
        so user ID byte must be read from the PoKeys device.
    """
    
    # Device Constants
    VENDOR_ID       = 0x1DC3
    PRODUCT_ID      = 0x1001
    INTERFACE_ID    = 1
    PACKET_LEN      = 64
    
    # Settings Constants
    pinsettings = ["Obsolete", "Digital input", "Digital output", "Analog input", "Analog output", "Triggered input", "Digital counter input", "Invert state"]

    def __init__(self):
        """Low level USB device access via PyUSB library. """
        self.deviceDescriptor = pokeys56U_DeviceDescriptor(self.VENDOR_ID, self.PRODUCT_ID, self.INTERFACE_ID)
        
        self.dev = self.deviceDescriptor.getDevice()
        
        if self.dev:
            print 'PoKeys56U device found'
            self.conf = self.dev.configurations[0]
            self.intf = self.conf.interfaces[self.INTERFACE_ID][0]
            self.endpoints = []
            for endpoint in self.intf.endpoints:
                self.endpoints.append(endpoint)
        else:
            print >> sys.stderr, "Cable isn't plugged in"
            sys.exit(0)
                
    def info(self):
        typeattr = ["Control", "Isochronous", "Bulk", "Interrupt"];
        """ Information on PoKeys56U """
        print "Device:", self.dev.filename
        print "  Device class:",self.dev.deviceClass
        print "  Device sub class:",self.dev.deviceSubClass
        print "  Device protocol:",self.dev.deviceProtocol
        print "  Max packet size:",self.dev.maxPacketSize
        print "  idVendor: %d (0x%04x)" % (self.dev.idVendor, self.dev.idVendor)
        print "  idProduct: %d (0x%04x)" % (self.dev.idProduct, self.dev.idProduct)
        print "  Device Version:",self.dev.deviceVersion
        for tconfig in self.dev.configurations:
			print "  Configuration:", tconfig.value
			print "    Total length:", tconfig.totalLength 
			print "    selfPowered:", tconfig.selfPowered
			print "    remoteWakeup:", tconfig.remoteWakeup
			print "    maxPower:", tconfig.maxPower
			for tintf in tconfig.interfaces:
				print "    Interface:",tintf[0].interfaceNumber
				for alt in tintf:
					print "    Alternate Setting:",alt.alternateSetting
					print "      Interface class:",alt.interfaceClass
					print "      Interface sub class:",alt.interfaceSubClass
					print "      Interface protocol:",alt.interfaceProtocol
					for ep in alt.endpoints:
						print "      Endpoint:",hex(ep.address)
						print "        Type:",typeattr[ep.type]
						print "        Max packet size:",ep.maxPacketSize
						print "        Interval:",ep.interval
                
    def open(self):
        """ Open PoKeys56U """
        
        self.handle = self.dev.open()
        if not self.handle:
            raise IOError("Open device failed")
        self.handle.reset()

        try:
            self.handle.claimInterface(self.intf)
            print 'open() : Claimed Interface'
        except usb.USBError:
            # claim interface failed, try detaching kernel driver first
            if not hasattr(self.handle, 'detachKernelDriver'):
                raise RuntimeError("Please upgrade pyusb to 0.4 or higher")
            try:
                self.handle.detachKernelDriver(self.intf)  
                self.handle.setConfiguration(self.conf)
                self.handle.claimInterface(self.intf)
                self.handle.setAltInterface(self.intf)
                print 'Claimed Interface with detachKernel'
            except usb.USBError:
                raise IOError("Claim interface failed")

        typeattr = ["Control", "Isochronous", "Bulk", "Interrupt"];
        # RaspBerry -> PoKeys56U
        self.ep_in = self.intf.endpoints[0]
        print "Endpoints IN Address ", hex(self.ep_in.address)
        print "Endpoints IN Type ", typeattr[self.ep_in.type]

        # PoKeys56U -> RaspBerry
        self.ep_out = self.intf.endpoints[1]
        print "Endpoints OUT Address ", hex(self.ep_out.address)
        print "Endpoints OUT Type ", typeattr[self.ep_out.type]

    def close(self):
        """ Release PoKeys56U interface """
        try:
            self.handle.releaseInterface()
            if not hasattr(self.handle, 'attachKernelDriver'):
                print "Not AttachKernelDriver function in pyusb 0.4, only pyusb 1.x"
            else:
                self.handle.attachKernelDriver(self.intf)
            print 'Released interface, USB connection closed'
        except Exception, err:
            # interface was not claimed. No problem
            print >> sys.stderr, err
        del self.handle
        del self.dev
        self.handle, self.dev = None, None

    def getManufactureName(self):
        """ Manufacturer of device """
        return self.handle.getString(self.dev.iManufacturer,30)

    def getProductName(self):
        """ Product name of device """
        return self.handle.getString(self.dev.iProduct,30)
    
    def getSerialNumber(self):
        """ Serial number of device """
        return self.handle.getString(self.dev.iSerialNumber,40)
    
    def readData(self, size):
        """ Receive data from the device. """
        print 'ReadData'
        
        result = self.handle.interruptRead(self.ep_in.address, size, 100)
        if result is None or len(result) < size:
            raise IOError('poKeys56U.device_libusb.USBDevice.read_data failed')
        return list(result)

    def writeData(self, data):
        """ Write data to device. """
        print 'WriteData'
        buffer = ([0,] * self.PACKET_LEN)
        buffer[0:8] = data[0:8]
        print buffer
        sent = self.handle.interruptWrite(self.ep_out.address, buffer, 1000)
        print 'Bytes written ', sent
        return sent
    
    def infoProtocole(self):
        """ Packet formatting Incoming and outgoing packets are 64bytes long.
            Basic packets useonly first8bytes.
            Extended packetsusethe whole packet.
            Host>Device
                - byte 1: control 0xBB
                - byte 2: operation
                - byte 3-6: operation parameters
                - byte 7: request ID
                - byte 8: control byte (sum after mod 0x100) 
    
            Device>Host
                - byte 1: control 0xAA
                - byte 2: operation
                - byte 3-6: operation parameters
                - byte 7: request ID
                - byte 8: control byte (sum after mod 0x100)
            Extended packetmode (supported since version 1.8)
            Packet size is increased to 64 bytes. 
            First 8 bytes remain the same, additional 56 bytes are us ed for extended mode.
        """
    
#    class pokeys56U_USBCommand(object):

    def list2bytes(self, d):
        return reduce(lambda a, b: a + b, map(lambda a: "%02X " % a, d))
 
    def list2char(self, d):
        return reduce(lambda a, b: a + b, map(lambda a: "%c" % a, d))

    def checksum(self, data):
        """ Checksum Trame """
        csum = 0
        for byte in data:
            csum += byte
        return (csum % 0x100)

    def GetDeviceIDEx(self, requestID):
        """ Read Serial of Device """
        try:
            print 'GetDeviceIDEx'
            msg = [0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
	        while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 0) and (read_bytes[6] == requestID) :
                        break
                print read_bytes
            return read_bytes[2:6]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00, 0x00, 0x00, 0x00]
    
    def SetUserID(self, userID, requestID):
        """ Retrieves user ID """
        try:
            print 'SetUserID'
            msg = [0xBB, 0x02, userID, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
	        while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 2) and (read_bytes[6] == requestID) :
                        break
                print read_bytes
            return read_bytes[2:3]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]
    
    def GetUserID(self, requestID):
        """ Retrieves user ID """
        try:
            print 'GetUserID'
            msg = [0xBB, 0x03, 0x00, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 3) and (read_bytes[6] == requestID) :
                        break
                print read_bytes
            return read_bytes[2:4]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00, 0x00]
    
    def SendPacket(self, msg, requestID):
        """ Function for GetReadBuildDate """
        try:
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
              while True:
                read_bytes = self.readData(self.PACKET_LEN)
                if (read_bytes[0] == 170) and (read_bytes[1] == 4) and (read_bytes[6] == requestID):
                    break
              print read_bytes
              return read_bytes[2:6]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00, 0x00, 0x00, 0x00]

    def GetReadBuildDate(self, requestID):
        """ Read Build Date of Device 
            Warning, after call this function, the new requestID is RequestID+3 """
        print 'GetReadBuilDateNew'
    
        msg = [0xBB, 0x04, 0x00, 0x00, 0x00, 0x00, requestID]
        msg.append(self.checksum(msg))
        read0 = self.SendPacket(msg, requestID)

        msg = [0xBB, 0x04, 0x01, 0x00, 0x00, 0x00, requestID+1]
        msg.append(self.checksum(msg))
        read1 = self.SendPacket(msg, requestID+1)

        msg = [0xBB, 0x04, 0x02, 0x00, 0x00, 0x00, requestID+2]
        msg.append(self.checksum(msg))
        read2 = self.SendPacket(msg, requestID+2)

        return self.list2char(read0)+self.list2char(read1)+self.list2char(read2)

    def GetSystemLoadStatus(self, requestID):
        """ Retrieves user ID """
        try:
            print 'GetSystemLoadStatus'
            msg = [0xBB, 0x05, 0x00, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 5) and (read_bytes[6] == requestID):
                        break
                print read_bytes
                return read_bytes[2:3]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]
    
 
    def SetPinData(self, pinID, pinFunction, requestID):
        """ Set pin data """
        try:
            print 'SetPinData'
            PinDataFunction = []
            for function in pinFunction:
                PinDataFunction.append(self.pinsettings.index(function))
            print PinDataFunction
        
            MaskFunction = 0
            for i in PinDataFunction:
                MaskFunction += pow(2, i)
            print MaskFunction
                    
            msg = [0xBB, 0x10, pinID, MaskFunction, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 16) and (read_bytes[6] == requestID):
                        break
                return read_bytes[2:3]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]

    def GetPinData(self, pinID, requestID):
        """ Get pin data """
        try:
            print 'GetPinData'
            PinData = []
            msg = [0xBB, 0x15, pinID, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 21) and (read_bytes[6] == requestID):
                        break
                PinData.append(read_bytes[2])
                for i in range(8):
                    if ((read_bytes[3] & (1<<i)) != 0):
                        PinData.append(self.pinsettings[i])
                return PinData
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]

    def SaveConfig(self, requestID):
        """ Configuration saving """
        try:
            print 'Config Saving'
            msg = [0xBB, 0x50, 0xAA, 0x55, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]
                       
    def GetInput(self, pinID, requestID):
        """ Reads current state of digital input """
        try:
            print 'GetIutput'
            msg = [0xBB, 0x30, pinID, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 48) and (read_bytes[6] == requestID):
                        break
                return read_bytes[2:4]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]

    def SetOutput(self, pinID, outputState, requestID):
        """ Sets digital output state
            <remarks>Before using this command, pin must be set as digital output. After power-up, 
            all pins are set as digital inputs due to security reasons.</remarks>
        """
        try:
            print 'SetOutput'
            msg = [0xBB, 0x40, pinID, outputState, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and (read_bytes[1] == 64) and (read_bytes[6] == requestID):
                        break
                return read_bytes[2:3]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]

    def BlockGetInput(self, block, requestID):
        """ Sets digital output state
            <remarks>Before using this command, pin must be set as digital output. After power-up, 
            all pins are set as digital inputs due to security reasons.</remarks>
        """
        try:
            print ('BlockGetInput', block)
            msg = [0xBB, 0x30+block, 0x00, 0x00, 0x00, 0x00, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and ((read_bytes[1] == 49) or (read_byte[1] == 50)) and (read_bytes[6] == requestID):
                        break
                return read_bytes[2:7]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]

    def BlockGetInput1(self, requestID):
        """ Read block of inputs (1 to 32) """
        bloc1 = self.BlockGetInput(0x01, requestID)
        return bloc1

    def BlockGetInput2(self, requestID):
        """ Read block of inputs (33 to 55) """
        bloc2 = self.BlockGetInput(0x02, requestID)
        return bloc2

    def BlockGetInputAll55(self, requestID):
        """ Read block of inputs (1 to 55) """        
        block1 = self.BlockGetInput(0x01, requestID)
        Block2 = self.BlockGetInput(0x02, requestID+1)
        return (block1, bloc2)

    def BlockSetOutput(self, block, outputState3, outputState4, outputState5, outputState6,requestID):
        """ Sets digital output state
            <remarks>Before using this command, pin must be set as digital output. After power-up, 
            all pins are set as digital inputs due to security reasons.</remarks>
        """
        try:
            print 'SetOutput'
            msg = [0xBB, 0x41+block, outputState3, outputState4, outputState5, outputState6, requestID]
            msg.append(self.checksum(msg)) # Add byte 8: sum after mod 0x100
            sent_bytes = self.writeData(msg)
            print sent_bytes
            if sent_bytes:
                while True:
                    read_bytes = self.readData(self.PACKET_LEN)
                    if (read_bytes[0] == 170) and ((read_bytes[1] == 66) or (read_byte[1] == 67)) and (read_bytes[6] == requestID):
                        break
                return read_bytes[2:3]
        except Exception, err:
            # Returns False on error.
            print >> sys.stderr, err
            return [0x00]

    def BlockGetOutput1(self, outputState3, outputState4, outputState5, outputState6, requestID):
        """ Set block of outputs (1 to 32) - only pins configured as digital outputs are affected by this command """
        bloc1 = self.BlockGetOnput(0x01, requestID)
        return bloc1

    def BlockGetOutput2(self, outputState3, outputState4, outputState5, outputState6, requestID):
        """ Set block of outputs (33 to 55) - only pins configured as digital outputs are affected by this command """
        bloc2 = self.BlockGetInput(0x02, requestID)
        return bloc2

    def BlockGetOutputAll55(self, outputState, requestID):
        """ Set block of outputs (1 to 55) - only pins configured as digital outputs are affected by this command """
        
class pokeys56U_DeviceDescriptor(object):
    """ Class for defining the USB device """
    
    def __init__(self, vendor_id, product_id, interface_id):
        """ Constructor """
        self.vendor_id = vendor_id
        self.product_id = product_id
        self.interface_id = interface_id
    
    def getDevice(self):
        """ Return the device corresponding to the device descriptor if it
            is available on the USB bus.  Otherwise return None. 
            Note that the returned device has yet to be claimed or opened.
        """
        # Find all the USB busses
        for bus in usb.busses():
            for device in bus.devices:
                if (device.idVendor == self.vendor_id and device.idProduct == self.product_id):
                        return device
        return None

#main method
def main():
  print ('pyPokeys56U', version)


#allows use as a module or stand-alone script
if __name__ == '__main__': main() 
